/* 
 * File:   File.h
 * Author: brady
 *
 * Created on March 12, 2011, 10:34 AM
 */



#ifndef FILE_H
#define	FILE_H

#include "OurDirectory.h"
#include "FileNotFoundException.h"

class File {
public:
    /**
     * if is true, creates a new file in with and opens the file wiith the specified openmode
     * if is false, attempts to open an existing file with the specified openmode
     * If file does not exist, throws FileNotFoundException.
     * @param dir directory containing the file
     * @param filename name of the file to be opened
     * @param mode mode to open the file in. Can be WRITE_ONLY, READ_ONLY, or CLOSED
     * @param create creates a new file if true, else opens an existing file
     */
    File(Directory *dir, char* filename, int mode, bool create)
    {

        //did we mean to pass a copy of dir instead of a pointer?
        //I changed it to a pointer. let me know if I'm wrong.
        m_directory = dir;
        m_fileName = filename;
        if(create)
        {
	    m_directory->GetFreeList()->lock();
            //get a new block off the freelist for the FCB
            m_fcb = m_directory->GetFreeList()->UnlinkBlock();
	    m_directory->GetFreeList()->unlock();
            /*
             * FCB looks like this:
             * [0:FileStartBlock]
             * [1:FileEndBlock]
             * [2:FileLength]
             */
            //make a new BlockGroup for the contents of the file
            //it will have 1 block
	    m_directory->GetFreeList()->lock();
            m_fileContents = m_directory->GetFreeList()->CreateNew();
	    m_directory->GetFreeList()->unlock();


            //file starts at this block
            m_fcb->SetPointer(m_fileContents->GetStartBlockNumber(), 0);

            //file ends at this block
            m_fcb->SetPointer(m_fileContents->GetEndBlockNumber(), 1);

            //the file has 0 bytes written to it.
            m_fcb->SetPointer(0, 2);

            //write new fcb to disk
	    m_fcb->Write(m_directory->GetFreeList()->getDisk());

            //now add this to the directory, probably should flush it too
            DirectoryEntry fileEntry = DirectoryEntry(filename, m_fcb->GetBlockNumber());
            cout<< "made it to add" << endl;
	    m_directory->flock();
	    m_directory->add(fileEntry);
	    cout<< "made it past add" << endl;
            m_directory->flush();
	    m_directory->funlock();
	    cout<< "made it past flush" << endl;
            m_filePtr = 0;
        }
        else
        {
            //Find the file in the directory
            int fcbNum = m_directory->Search(filename);

            //handle case where file doesn't exist
            if(fcbNum < 0)
            {
                throw FileNotFoundException();
            }
            //get the file's FCB off the disk
            m_fcb = new Block(fcbNum, dir->GetFreeList()->getDisk());

            //now build the BlockGroup of the file's data:

            //compute the actual number of blocks used by the file
            //using integer division
            //note: we can't just take endBlock - startBlock 
            //      because the blocks might be out of order
            int byteCount = m_fcb->GetPointer(2);

            int blockCount = (byteCount / 508) + 1;

	    if(mode == READ_ONLY){
	    m_filePtr = 0;
	    }
            else
            {
                m_filePtr = byteCount;
            }

            //now reconstruct the file's data blockgroup
            this->m_fileContents = new BlockGroup(m_fcb->GetPointer(0), m_fcb->GetPointer(1), blockCount, m_directory->GetFreeList());
        }

        //finally,set the openmode
        this->m_openMode = mode;
    }

    ~File(){
	delete m_fcb;
	delete m_fileContents;
     }

    char* GetFileName()
    {
        char* fname = new char;
        strcpy(fname, m_fileName);
        return fname;
    }

    /**
     * closes the open file. The file will no longer be able to be read or written to.
     * @return true on success, false on error.
     */
    bool Close()
    {
      //Set file to closed
      m_openMode = CLOSED;

       //Close the RAM representation of the file
            //the blockgroup is deleted and set to NULL

      //set file pointer to -1
      // m_filePtr = -1;

	    return true;
    }

int GetSize(){
	return m_fcb->GetPointer(2);
}

    /**
     * Reads bytes from the file into
     * @param buffer buffer to store the data read from the file
     * @param length number of bytes to read from the file
     * @return number returns number of bytes read into .
     * If number returned is less than length, EOF was reached.
     * otherwise returns -1 on error.
     */
    int Read(char* buffer, int length)
    {
         /* int curBlock = filePtr / 508
          * int curByte = filePtr % 508
          * if(closed) return -1
          * else
          * if (at end of file) return 0
          * else
          * filePtr + length is the byte to read up to.

          * if it is greater than the length of our file, read up to EOF,
          * and adjust our return number accordingly
          * set tempLength = length
          * for each block
          * if tempLength >= 508, copy the whole block, tempLength = tempLength - 508
          * if tempLength < 508, copy tempLength bytes into the buffer
          *
          * return length
          *
          */


      //initializing the current block
      int curBlock = m_filePtr/508;

      //initializing the current byte of block we are at
      int curByte = m_filePtr%508;

      //initializing offset for destination buffer
      int bufferOffset = 0;

      //fileSize is the size of the file in bytes
      int fileSize = m_fcb->GetPointer(2);

      int bytesRead = 0;

      //if their is a remainder the pointer is actually in the next block
      //if(curByte > 0)curBlock += 1;
      
      //check to see if closed
      if(m_openMode == CLOSED || m_openMode == WRITE_ONLY){
	return -1;
      }
      //file is opened
      else{
	//check to see if file pointer is at the end of the file
	if(curBlock == m_fileContents->GetNumberOfBlocks()+1 && curByte ==0){
	  //filePtr is at the end of file
	  //end of file is when the filePtr points one byte past our file
	  //NOTE: byte at filePtr, when its at the end of file, is not pointing to a byte in the file
	  return 0;
	}
	//filePtr is not at the end of file
	else{
	  //endByte is the first Byte that is not read
	  int endByte = m_filePtr + length;
	  //if it will read past the end of file
	  if(endByte > fileSize){
	    //we shorten the length we will read to the end of the file
	    length = fileSize - m_filePtr;
	  }
	  //will not read past end of file
	  int tempLength = length;//length left to read in file	  

	  //iterate through the blocks until curBlock is reached
	  m_fileContents->Rewind();
	  for(int i = 0; i<curBlock; i++){
	    m_fileContents->GetNextBlock();
	  }

	  //iterate through the blocks of the list
          int blockCount = 0;
	  for(int i = 0; i < m_fileContents->GetNumberOfBlocks(); i++){
              blockCount++;

	    if(tempLength >= (508 - curByte)){
	      //Length left to read is greater than or equal to
	      //what is left in block
	      Block* curBlk = m_fileContents->GetCurrentBlock();

	      //toCopy is number of bytes from this block which will be copied
	      int toCopy = 508 - curByte;
	      //offset of the read of the block
	      int copyOffset = curByte + 4;
	      char* curBlkBuffer = (char*)curBlk->GetBuffer();
	      memcpy(&buffer[bufferOffset], &curBlkBuffer[copyOffset], toCopy);
              bytesRead += toCopy;
	      //since whole block was read in curByte is set to 0 or next block
	      curByte = 0;
	      //whole block is read so we need to go to the next block
	      m_fileContents->GetNextBlock();
	      //update the bufferOffset
	      bufferOffset += toCopy;
	      //update tempLength
	      tempLength -= toCopy;
	      
	    }
	    else if(tempLength < (508 - curByte)&&tempLength >= 0){
	      //Length left to read is less than what is left in the block
	      Block* curBlk = m_fileContents->GetCurrentBlock();

	      //toCopy is the number of bytes to be copied from this block
	      int toCopy = tempLength;
	      //offset of the read of the block
	      int copyOffset = curByte +4;
	      char* curBlkBuffer = (char*)curBlk->GetBuffer();

	      memcpy(&buffer[bufferOffset], &curBlkBuffer[copyOffset], toCopy);
              bytesRead += toCopy;
	      //at this point we have read in length amount of bytes
	      //now we can break out of the for loop
	      break;
	      
	    }
	    else{
	      //invalid length
	      cout<<"Invalid length error\n";
	    }
            
	  }//end for
	  cout<< "This data spans "<< blockCount << " blocks\n";
	  //update filePtr
	  m_filePtr = m_filePtr + length;
	  
	  //return length read
	  return bytesRead;

	}
	
      }
    

    }//end of read

    /**
     * Writes bytes from the into the file
     * @param buffer buffer containing the data to be written to the file
     * @param length number of bytes to be written to the file
     * @return returns -1 on error, otherwise returns number of bytes written to the file 
     */
    int Write(char* buffer, int length)
    {
        /*
         * int tempLength = length
         * while(tempLength > bytes left in the last block){
         *   int toCopy = filePtr % 508
         *   copy toCopy bytes into the block
         *   tempLenght = tempLenght - toCopy
         *   if(tempLength > 0)
         *   then add another block to the file
         * }
         * write the remaining bytes to the last block
         */


      //initializing tempLength to no. of bytes left to write
      int tempLength = length;

      //initializing bufferOffset to the offset of the source buffer
      int bufferOffset = 0;

      //check to see if file is open
      if(m_openMode == CLOSED || m_openMode == READ_ONLY){
	return -1;
      }
      //make sure its not read_only
      else if(m_openMode == READ_ONLY){
	return -1;
      }
      else{
	//the file is write_only

	//initializes curBlock, which is the end block
	Block curBlock = Block(m_fileContents->GetEndBlockNumber(),
			       m_directory->GetFreeList()->getDisk());

        while(tempLength > (508 - m_filePtr%508)){
	  //there is more bytes to be written than remaining 
	  //bytes in the last block

	  //toCopy number of bytes to be copied into this block
	  //total bytes in block minus bytes being used
	  int toCopy = 508 - (m_filePtr%508);

	  //blkOffset is the offset into the block buffer
	  int blkOffset = m_filePtr%508 + 4;

	  //get the buffer from curBlock
	  char* destBuf = (char*)curBlock.GetBuffer();

	  //copy the bytes into the blockBuffer
	  memcpy(&destBuf[blkOffset], &buffer[bufferOffset], toCopy);

	  //set buffer of curBlock and write it to disk
	  curBlock.SetBuffer((unsigned char*)destBuf);
	  curBlock.Write(m_directory->GetFreeList()->getDisk());

	  //add new block and reinitialize curBlock to the new last block
	  m_directory->GetFreeList()->lock();
	  m_fileContents->AddNewBlock();
	  m_directory->GetFreeList()->unlock();
          m_fcb->SetPointer(m_fileContents->GetEndBlockNumber(),1);
	  curBlock = Block(m_fileContents->GetEndBlockNumber(),
			       m_directory->GetFreeList()->getDisk());

	  //update m_filePtr, tempLength and bufferOffset
	  m_filePtr += toCopy;
	  bufferOffset += toCopy;
          tempLength -= toCopy;
	  
	  }//end while


	//write the remaining bytes to the last block

	//blkOffset is the offset into the destination buffer
	int blkOffset = m_filePtr%508 + 4;

	//toCopy is the number of bytes needed to copy
	int toCopy = tempLength;
	
	//get the buffer from the last block
	//NOTE: if there is zero bytes to be written in the last block it will
	//preform this writing but it will copy zero bytes
	char* destBuf = (char*)curBlock.GetBuffer();
	memcpy(&destBuf[blkOffset], &buffer[bufferOffset], toCopy);

	int* point = (int*)destBuf;

	//set buffer of curBlock and write it to disk
        curBlock.SetBuffer((unsigned char*)destBuf);
        curBlock.Write(m_directory->GetFreeList()->getDisk());

        //update filePtr
	m_filePtr += toCopy;

	//update the fcb
	m_fcb->SetPointer(m_fileContents->GetStartBlockNumber(),0);//new start block
	m_fcb->SetPointer(m_fileContents->GetEndBlockNumber(),1);//new end block
	m_fcb->SetPointer(m_filePtr,2);//new file length

	//write new fcb to disk
	m_fcb->Write(m_directory->GetFreeList()->getDisk());

	return length;

      }//end else

    }//end of write


    /**
     * return's the File's data blockGroup to the freelist,
     * as well as the file's file control block
     */

    void Remove()
    {
	m_directory->GetFreeList()->lock();
	cout << "made it past first remove lock" << endl;
        //return the file blocks
        m_directory->GetFreeList()->Return(m_fileContents);
        
        //fcb will be endblock on the freelist, pointer = -1
        m_directory->GetFreeList()->AddBlock(m_fcb);
	m_directory->GetFreeList()->unlock();	

        //deallocate RAM representations
        //delete the RAM fcb
        delete m_fcb;
        m_fcb = NULL;

        //delete the RAM blockgroup
        delete m_fileContents;
        m_fileContents = NULL;
        
        //have this file remove itself from the directory;
	m_directory->flock();
        m_directory->remove(m_fileName);
	cout<< "made it past remove" << endl;
        m_directory->flush();
	m_directory->funlock();
    }

    
    int GetFilePointer()
    {
			return m_filePtr;
	}
	
	void SetFilePointer(int newFilePtr)
	{
			m_filePtr = newFilePtr;
	}
    
    static const int READ_ONLY = 1;
    static const int WRITE_ONLY = 2;
    static const int CLOSED = 0;
private:
    int m_openMode;
    Directory* m_directory;
    Block* m_fcb;
    BlockGroup* m_fileContents;
    int m_filePtr;
    char* m_fileName;





};

#endif	/* FILE_H */

